home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-01-17 | 54.5 KB | 1,566 lines | [TEXT/R*ch] |
- Archive-name: C++-faq/part3
- Posting-Frequency: monthly
- Last-modified: Jan 1, 1997
- URL: http://www.cerfnet.com/~mpcline/c++-faq-lite/
-
- AUTHOR: Marshall Cline / cline@parashift.com / Paradigm Shift, Inc. /
- One Park St. / Norwood, NY 13668 / 315-353-6100 (voice) / 315-353-6110 (fax)
-
- COPYRIGHT: This posting is part of "C++ FAQ Lite." The entire "C++ FAQ Lite"
- document is Copyright(C) 1991-96 Marshall P. Cline, Ph.D., cline@parashift.com.
- All rights reserved. Copying is permitted only under designated situations.
- For details, see section [1].
-
- NO WARRANTY: THIS WORK IS PROVIDED ON AN "AS IS" BASIS. THE AUTHOR PROVIDES NO
- WARRANTY WHATSOEVER, EITHER EXPRESS OR IMPLIED, REGARDING THE WORK, INCLUDING
- WARRANTIES WITH RESPECT TO ITS MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
- PURPOSE.
-
- C++-FAQ-Lite != C++-FAQ-Book: This document, C++ FAQ Lite, is not the same as
- the C++ FAQ Book. The book (C++ FAQs, Cline and Lomow, Addison-Wesley) is 500%
- larger than this document, and is available in bookstores. For details, see
- section [3].
-
- ==============================================================================
-
- SECTION [7]: Classes and objects
-
-
- [7.1] What is a class?
-
- The fundamental building block of OO software.
-
- A class defines a data type, much like a struct would be in C. In a computer
- science sense, a type consists of both a set of states and a set of operations
- which transition between those states. Thus int is a type because it has both
- a set of states and it has operations like i + j or i++, etc. In exactly the
- same way, a class provides a set of (usually public:) operations, and a set of
- (usually non-public:) data bits representing the abstract values that instances
- of the type can have.
-
- Think of int as a class that has member functions called operator++, etc.
-
- Note: a C programmer can think of a class as a C struct whose members default
- to private. But if that's all you think of a class, then you probably need to
- experience a personal paradigm shift.
-
- ==============================================================================
-
- [7.2] What is an object?
-
- A region of storage with associated semantics.
-
- After the declaration int i; we say that "i is an object of type int." In
- OO/C++, "object" usually means "an instance of a class." Thus a class defines
- the behavior of possibly many objects (instances).
-
- ==============================================================================
-
- [7.3] When is an interface "good"?
-
- When it provides a simplified view of a chunk of software, and it is expressed
- in the vocabulary of a user (where a "chunk" is normally a class or a tight
- group of classes[14.2], and a "user" is another developer rather than the
- ultimate customer).
- * The "simplified view" means unnecessary details are intentionally hidden.
- This reduces the user's defect-rate.
- * The "vocabulary of users" means users don't need to learn a new set of words
- and concepts. This reduces the user's learning curve.
-
- ==============================================================================
-
- [7.4] What is encapsulation?
-
- Preventing unauthorized access to some piece of information or functionality.
-
- The key money-saving insight is to separate the volatile part of some chunk of
- software from the stable part. Encapsulation puts a firewall around the chunk,
- which prevents other chunks from accessing the volatile parts; other chunks can
- only access the stable parts. This prevents the other chunks from breaking if
- (when!) the volatile parts are changed. In context of OO software, a "chunk"
- is normally a class or a tight group of classes[14.2].
-
- The "volatile parts" are the implementation details. If the chunk is a single
- class, the volatile part is normally encapsulated using the private: and/or
- protected: keywords[19.5]. If the chunk is a tight group of classes[14.2],
- encapsulation can be used to deny access to entire classes in that group.
- Inheritance[19] can also be used as a form of encapsulation[22.2].
-
- The "stable parts" are the interfaces. A good interface provides a simplified
- view in the vocabulary of a user[7.3], and is designed from the
- outside-in[13.9] (here a "user" means another developer, not the end-user who
- buys the completed application). If the chunk is a single class, the interface
- is simply the class's public: member functions and friend[14] functions. If
- the chunk is a tight group of classes[14.2], the interface can include several
- of the classes in the chunk.
-
- Designing a clean interface and separating that interface from its
- implementation[22.1] merely allows users to use the interface. But
- encapsulating (putting "in a capsule") the implementation forces users to use
- the interface.
-
- ==============================================================================
-
- [7.5] How does C++ help with the tradeoff of safety vs. usability?
-
- In C, encapsulation[7.4] was accomplished by making things static in a
- compilation unit or module. This prevented another module from accessing the
- static stuff. (By the way, that use is now depreciated: don't do that in C++.)
-
- Unfortunately this approach doesn't support multiple instances of the data,
- since there is no direct support for making multiple instances of a module's
- static data. If multiple instances were needed in C, programmers typically
- used a struct. But unfortunately C structs don't support encapsulation[7.4].
- This exacerbates the tradeoff between safety (information hiding) and usability
- (multiple instances).
-
- In C++, you can have both multiple instances and encapsulation via a class.
- The public: part of a class contains the class's interface, which normally
- consists of the class's public: member functions and its friend[14] functions.
- The private: and/or protected:[19.5] parts of a class contain the class's
- implementation, which is typically where the data lives.
-
- The end result is like an "encapsulated struct." This reduces the tradeoff
- between safety (information hiding) and usability (multiple instances).
-
- ==============================================================================
-
- [7.6] How can I prevent other programmers from violating encapsulation by
- seeing the private parts of my class?
-
- Not worth the effort -- encapsulation is for code, not people.
-
- It doesn't violate encapsulation for a programmer to see the private: and/or
- protected:[19.5] parts of your class, so long as they don't write code that
- somehow depends on what they saw. In other words, encapsulation doesn't
- prevent people from knowing about the inside of a class; it prevents the code
- they write from becoming dependent on the insides of the class. Your company
- doesn't have to pay a "maintenance cost" to maintain the gray matter between
- your ears; but it does have to pay a maintenance cost to maintain the code that
- comes out of your finger tips. What you know as a person doesn't increase
- maintenance cost, provided the code they write depends on the interface rather
- than the implementation.
-
- Besides, this is rarely if ever a problem. I don't know any programmers who
- have intentionally tried to accessed the private parts of a class. "My
- recommendation in such cases would be to change the programmer, not the code"
- [James Kanze, kanze@gabi-soft.fr; used with permission].
-
- ==============================================================================
-
- [7.7] Is Encapsulation a Security device?
-
- No.
-
- Encapsulation != security.
-
- Encapsulation prevents mistakes, not espionage.
-
- ==============================================================================
-
- [7.8] What's the difference between the keywords struct and class?
-
- The members and base classes of a struct are public by default, while in class,
- they default to private. Note: you should make your base classes explicitly
- public, private, or protected, rather than relying on the defaults.
-
- struct and class are otherwise functionally equivalent.
-
- OK, enough of that squeaky clean techno talk. Emotionally, most developers
- make a strong distinction between a class and a struct. A struct simply feels
- like an open pile of bits with very little in the way of encapsulation or
- functionality. A class feels like a living and responsible member of society
- with intelligent services, a strong encapsulation barrier, and a well defined
- interface. Since that's the connotation most people already have, you should
- probably use the struct keyword if you have a class that has very few methods
- and has public data (such things do exist in well designed systems!), but
- otherwise you should probably use the class keyword.
-
- ==============================================================================
-
- SECTION [8]: References
-
-
- [8.1] What is a reference?
-
- An alias (an alternate name) for an object.
-
- References are frequently used for pass-by-reference:
-
- void swap(int& i, int& j)
- {
- int tmp = i;
- i = j;
- j = tmp;
- }
-
- main()
- {
- int x, y;
- // ...
- swap(x,y);
- }
-
- Here i and j are aliases for main's x and y respectively. In other words, i is
- x -- not a pointer to x, nor a copy of x, but x itself. Anything you do to i
- gets done to x, and vice versa.
-
- OK. That's how you should think of references as a programmer. Now, at the
- risk of confusing you by giving you a different perspective, here's how
- references are implemented. Underneath it all, a reference i to object x is
- typically the machine address of the object x. But when the programmer says
- i++, the compiler generates code that increments x. In particular, the address
- bits that the compiler uses to find x are not changed. A C programmer will
- think of this as if you used the C style pass-by-pointer, with the syntactic
- variant of (1) moving the & from the caller into the callee, and (2)
- eliminating the *s. In other words, a C programmer will think of i as a macro
- for (*p), where p is a pointer to x (e.g., the compiler automatically
- dereferences the underlying pointer; i++ is changed to (*p)++; i = 7 is
- automatically changed to *p = 7).
-
- Important note: Even though a reference is often implemented using an address
- in the underlying assembly language, please do not think of a reference as a
- funny looking pointer to an object. A reference is the object. It is not a
- pointer to the object, nor a copy of the object. It is the object.
-
- ==============================================================================
-
- [8.2] What happens if you assign to a reference?
-
- You change the referent (the object to which the reference refers).
-
- Remember: the reference is the referent, so changing the reference changes the
- referent. In compiler writer lingo, a reference is an "lvalue" (something that
- can appear on the left hand side of an assignment operator).
-
- ==============================================================================
-
- [8.3] What happens if you return a reference?
-
- The function call can appear on the left hand side of an assignment operator.
-
- This ability may seem strange at first. For example, no one thinks the
- expression f() = 7 makes sense. Yet, if a is an object of class Array, most
- people think that a[i] = 7 makes sense even though a[i] is really just a
- function call in disguise (it calls Array::operator[](int), which is the
- subscript operator for class Array).
-
- class Array {
- public:
- int size() const;
- float& operator[] (int index);
- // ...
- };
-
- main()
- {
- Array a;
- for (int i = 0; i < a.size(); ++i)
- a[i] = 7; // This line invokes Array::operator[](int)
- }
-
- ==============================================================================
-
- [8.4] How can you reseat a reference to make it refer to a different object?
- [UPDATED!]
-
- [Recently changed last line from "pointer to const" to "const pointer" thanks
- to Neelesh Pandit (on 11/96).]
-
- No way.
-
- You can't separate the reference from the referent.
-
- Unlike a pointer, once a reference is bound to an object, it can not be
- "reseated" to another object. The reference itself isn't an object (it has no
- identity; taking the address of a reference gives you the address of the
- referent; remember: the reference is its referent).
-
- In that sense, a reference is similar to a const pointer[18.5] such as
- int* const p (as opposed to a pointer to const[18.4] such as const int* p). In
- spite of the gross similarity, please don't confuse references with pointers;
- they're not at all the same.
-
- ==============================================================================
-
- [8.5] When should I use references, and when should I use pointers?
-
- Use references when you can, and pointers when you have to.
-
- References are usually preferred over pointers whenever you don't need
- "reseating"[8.4]. This usually means that references are most useful in a
- class's public interface. References typically appear on the skin of an
- object, and pointers on the inside.
-
- The exception to the above is where a function's parameter or return value
- needs a "sentinel" reference. This is usually best done by returning/taking a
- pointer, and giving the NULL pointer this special significance (references
- should always alias objects, not a dereferenced NULL pointer).
-
- Note: Old line C programmers sometimes don't like references since they provide
- reference semantics that isn't explicit in the caller's code. After some C++
- experience, however, one quickly realizes this is a form of information hiding,
- which is an asset rather than a liability. E.g., programmers should write code
- in the language of the problem rather than the language of the machine.
-
- ==============================================================================
-
- SECTION [9]: Inline functions
-
-
- [9.1] What's the deal with inline functions?
-
- An inline function is a function whose code gets inserted into the caller's
- code stream. Like a #define macro, inline functions improve performance by
- avoiding the overhead of the call itself and (especially!) by the compiler
- being able to optimize through the call ("procedural integration").
-
- ==============================================================================
-
- [9.2] How can inline functions help with the tradeoff of safety vs. speed?
-
- In straight C, you can achieve "encapsulated structs" by putting a void* in a
- struct, in which case the void* points to the real data that is unknown to
- users of the struct. Therefore users of the struct don't know how to interpret
- the stuff pointed to by the void*, but the access functions cast the void* to
- the approprate hidden type. This gives a form of encapsulation.
-
- Unfortunately it forfeits type safety, and also imposes a function call to
- access even trivial fields of the struct (if you allowed direct access to the
- struct's fields, anyone and everyone would be able to get direct access since
- they would of necessity know how to interpret the stuff pointed to by the
- void*; this would make it difficult to change the underlying data structure).
-
- Function call overhead is small, but can add up. C++ classes allow function
- calls to be expanded inline. This lets you have the safety of encapsulation
- along with the speed of direct access. Furthermore the parameter types of
- these inline functions are checked by the compiler, an improvement over C's
- #define macros.
-
- ==============================================================================
-
- [9.3] Why should I use inline functions? Why not just use plain old #define
- macros?
-
- Because #define macros are evil.
-
- Unlike #define macros, inline functions avoid infamous macro errors since
- inline functions always evaluate every argument exactly once. In other words,
- invoking an inline function is semantically just like invoking a regular
- function, only faster:
-
- // A macro that returns the absolute value of i
- #define unsafe(i) \
- ( (i) >= 0 ? (i) : -(i) )
-
- // An inline function that returns the absolute value of i
- inline
- int safe(int i)
- {
- return i >= 0 ? i : -i;
- }
-
- int f();
-
- void userCode(int x)
- {
- int ans;
-
- ans = unsafe(x++); // Error! x is incremented twice
- ans = unsafe(f()); // Danger! f() is called twice
-
- ans = safe(x++); // Correct! x is incremented once
- ans = safe(f()); // Correct! f() is called once
- }
-
- Also unlike macros, argument types are checked, and necessary conversions are
- performed correctly.
-
- Macros are bad for your health; don't use them unless you have to.
-
- ==============================================================================
-
- [9.4] How do you tell the compiler to make a non-member function inline?
-
- When you declare an inline function, it looks just like a normal function:
-
- void f(int i, char c);
-
- But when you define an inline function, you prepend the function's definition
- with the keyword inline, and you put the definition into a header file:
-
- inline
- void f(int i, char c)
- {
- // ...
- }
-
- Note: It's imperative that the function's definition (the part between the
- {...}) be placed in a header file, unless the function is used only in a single
- .cpp file. In particular, if you put the inline function's definition into a
- .cpp file and you call it from some other .cpp file, you'll get an "unresolved
- external" error from the linker.
-
- ==============================================================================
-
- [9.5] How do you tell the compiler to make a member function inline?
-
- When you declare an inline member function, it looks just like a normal member
- function:
-
- class Fred {
- public:
- void f(int i, char c);
- };
-
- But when you define an inline member function, you prepend the member
- function's definition with the keyword inline, and you put the definition into
- a header file:
-
- inline
- void Fred::f(int i, char c)
- {
- // ...
- }
-
- It's usually imperative that the function's definition (the part between the
- {...}) be placed in a header file. If you put the inline function's definition
- into a .cpp file, and if it is called from some other .cpp file, you'll get an
- "unresolved external" error from the linker.
-
- ==============================================================================
-
- [9.6] Is there another way to tell the compiler to make a member function
- inline?
-
- Yep: define the member function in the class body itself:
-
- class Fred {
- public:
- void f(int i, char c)
- {
- // ...
- }
- };
-
- Although this is easier on the person who writes the class, it's harder on all
- the readers since it mixes "what" a class does with "how" it does them.
- Because of this mixture, we normally prefer to define member functions outside
- the class body with the inline keyword[9.5]. The insight that makes sense of
- this: in a reuse-oriented world, there will usually be many people who use your
- class, but there is only one person who builds it (yourself); therefore you
- should do things that favor the many rather than the few.
-
- ==============================================================================
-
- [9.7] Are inline functions guaranteed to make your performance better?
-
- Nope.
-
- Beware that overuse of inline functions can cause code bloat, which can in turn
- have a negative performance impact in paging environments.
-
- ==============================================================================
-
- SECTION [10]: Constructors
-
-
- [10.1] What's the deal with constructors?
-
- Constructors build objects from dust.
-
- Constructors are like "init functions". They turn a pile of arbitrary bits
- into a living object. Minimally they initialize internally used fields. They
- may also allocate resources (memory, files, semaphores, sockets, etc).
-
- "ctor" is a typical abbreviation for constructor.
-
- ==============================================================================
-
- [10.2] Is there any difference between List x; and List x();?
-
- A big difference!
-
- Suppose that List is the name of some class. Then function f() declares a
- local List object called x:
-
- void f()
- {
- List x; // Local object named x (of class List)
- // ...
- }
-
- But function g() declares a function called x() that returns a List:
-
- void g()
- {
- List x(); // Function named x (that returns a List)
- // ...
- }
-
- ==============================================================================
-
- [10.3] How can I make a constructor call another constructor as a primitive?
-
- No way.
-
- Dragons be here: if you call another constructor, the compiler initializes a
- temporary local object; it does not initialize this object. You can combine
- both constructors by using a default parameter, or you can share their common
- code in a private init() member function.
-
- ==============================================================================
-
- [10.4] Is the default constructor for Fred always Fred::Fred()?
-
- No. A "default constructor" is a constructor that can be called with no
- arguments. Thus a constructor that takes no arguments is certainly a default
- constructor:
-
- class Fred {
- public:
- Fred(); // Default constructor: can be called with no args
- // ...
- };
-
- However it is possible (and even likely) that a default constructor can take
- arguments, provided they are given default values:
-
- class Fred {
- public:
- Fred(int i=3, int j=5); // Default constructor: can be called with no args
- // ...
- };
-
- ==============================================================================
-
- [10.5] Which constructor gets called when I create an array of Fred objects?
-
- Fred's default constructor[10.4].
-
- There is no way to tell the compiler to call a different constructor. If your
- class Fred doesn't have a default constructor[10.4], attempting to create an
- array of Fred objects is trapped as an error at compile time.
-
- class Fred {
- public:
- Fred(int i, int j);
- // ... assume there is no default constructor[10.4] in class Fred ...
- };
-
- main()
- {
- Fred a[10]; // ERROR: Fred doesn't have a default constructor
- Fred* p = new Fred[10]; // ERROR: Fred doesn't have a default constructor
- }
-
- However if you are creating an STL[32.1] vector<Fred> rather than an array of
- Fred (which you probably should be doing anyway since arrays are evil[21.5]),
- you don't have to have a default constructor in class Fred, since you can give
- the vector a Fred object to be used to initialize the elements:
-
- #include <vector>
- using namespace std;
-
- main()
- {
- vector<Fred> a(10, Fred(5,7));
- // The 10 Fred objects in vector a will be initialized with Fred(5,7).
- // ...
- }
-
- ==============================================================================
-
- [10.6] What is the "Named Constructor Idiom"?
-
- A technique that provides more intuitive and/or safer construction operations
- for users of your class.
-
- The problem is that constructors always have the same name as the class.
- Therefore the only way to differentiate between the various constructors of a
- class is by the parameter list. But if there are lots of constructors, the
- differences between the constructors becomes somewhat subtle and error prone.
-
- With the Named Constructor Idiom, you declare all the class's constructors in
- the private: or protected: sections, and you provide public static methods that
- return an object. These static methods are the so-called "Named Constructors."
- In general there is one such static method for each different way to construct
- an object.
-
- For example, suppose we are building a Point class that represents a position
- on the X-Y plane. Turns out there are two common ways to specify a 2-space
- coordinate: rectangular coordinates (X+Y), polar coordinates (Radius+Angle).
- (Don't worry if you can't remember these; the point isn't the particulars of
- coordinate systems; the point is that there are several ways to create a Point
- object). Unfortunately the parameters for these two coordinate systems are the
- same: two floats. This would create an ambiguity error in the overloaded
- constructors:
-
- class Point {
- public:
- Point(float x, float y); // Rectangular coordinates
- Point(float r, float a); // Polar coordinates (radius and angle)
- // ERROR: Overload is Ambiguous: Point::Point(float,float)
- };
-
- main()
- {
- Point p = Point(5.7, 1.2); // Ambiguous: Which coordinate system?
- }
-
- One way to solve this ambiguity is to use the Named Constructor Idiom:
-
- #include <math.h> // To get sin() and cos()
-
- class Point {
- public:
- static Point rectangular(float x, float y); // Rectangular coord's
- static Point polar(float radius, float angle); // Polar coordinates
- // These static methods are the so-called "named constructors"
- // ...
- private:
- Point(float x, float y); // Rectangular coordinates
- float x_, y_;
- };
-
- inline Point::Point(float x, float y)
- : x_(x), y_(y) { }
-
- inline Point Point::rectangular(float x, float y)
- { return Point(x, y); }
-
- inline Point Point::polar(float radius, float angle)
- { return Point(radius*cos(angle), radius*sin(angle)); }
-
- Now the users of Point have a clear and unambiguous syntax for creating Points
- in either coordinate system:
-
- main()
- {
- Point p1 = Point::rectangular(5.7, 1.2); // Obviously rectangular
- Point p2 = Point::polar(5.7, 1.2); // Obviously polar
- }
-
- Make sure your constructors are in the protected: section if you expect Fred to
- have derived classes.
-
- The Named Constructor Idiom can also be used to make sure your objects are
- always created via new[16.18].
-
- ==============================================================================
-
- [10.7] Why can't I initialize my static member data in my constructor's
- initialization list?
-
- Because you must explicitly define your class's static data members.
-
- Fred.h:
-
- class Fred {
- public:
- Fred();
- // ...
- private:
- int i_;
- static int j_;
- };
-
- Fred.cpp (or Fred.C or whatever):
-
- Fred::Fred()
- : i_(10) // OK: you can (and should) initialize member data this way
- j_(42) // Error: you cannot initialize static member data like this
- {
- // ...
- }
-
- // You must define static data members this way:
- int Fred::j_ = 42;
-
- ==============================================================================
-
- [10.8] Why are classes with static data members getting linker errors?
-
- Because static data members must be explicitly defined in exactly one
- compilation unit[10.7]. If you didn't do this, you'll probably get an
- "undefined external" linker error. For example:
-
- // Fred.h
-
- class Fred {
- public:
- // ...
- private:
- static int j_; // Declares static data member Fred::j_
- // ...
- };
-
- The linker will holler at you ("Fred::j_ is not defined") unless you define (as
- opposed to merely declare) Fred::j_ in (exactly) one of your source files:
-
- // Fred.cpp
-
- #include "Fred.h"
-
- int Fred::j_ = some_expression_evaluating_to_an_int;
-
- // Alternatively, if you wish to use the implicit 0 value for static ints:
- // int Fred::j_;
-
- The usual place to define static data members of class Fred is file Fred.cpp
- (or Fred.C or whatever source file extension you use).
-
- ==============================================================================
-
- SECTION [11]: Destructors
-
-
- [11.1] What's the deal with destructors?
-
- A destructor gives an object its last rites.
-
- Destructors are used to release any resources allocated by the object. E.g.,
- class Lock might lock a semaphore, and the destructor will release that
- semaphore. The most common example is when the constructor uses new, and the
- destructor uses delete.
-
- Destructors are a "prepare to die" member function. They are often abbreviated
- "dtor".
-
- ==============================================================================
-
- [11.2] What's the order that local objects are destructed?
-
- In reverse order of construction: First constructed, last destructed.
-
- In the following example, b's destructor will be executed first, then a's
- destructor:
-
- void userCode()
- {
- Fred a;
- Fred b;
- // ...
- }
-
- ==============================================================================
-
- [11.3] What's the order that objects in an array are destructed?
-
- In reverse order of construction: First constructed, last destructed.
-
- In the following example, the order for destructors will be a[9], a[8], ...,
- a[1], a[0]:
-
- void userCode()
- {
- Fred a[10];
- // ...
- }
-
- ==============================================================================
-
- [11.4] Can I overload the destructor for my class?
-
- No.
-
- You can have only one destructor for a class Fred. It's always called
- Fred::~Fred(). It never takes any parameters, and it never returns anything.
-
- You can't pass parameters to the destructor anyway, since you never explicitly
- call a destructor[11.5] (well, almost never[11.10]).
-
- ==============================================================================
-
- [11.5] Should I explicitly call a destructor on a local variable?
-
- No!
-
- The destructor will get called again at the close } of the block in which the
- local was created. This is a guarantee of the language; it happens
- automagically; there's no way to stop it from happening. But you can get
- really bad results from calling a destructor on the same object a second time!
- Bang! You're dead!
-
- ==============================================================================
-
- [11.6] What if I want a local to "die" before the close } of the scope in which
- it was created? Can I call a destructor on a local if I really want to?
-
- No! [For context, please read the previous FAQ[11.5]].
-
- Suppose the (desirable) side effect of destructing a local File object is to
- close the File. Now suppose you have an object f of a class File and you want
- File f to be closed before the end of the scope (i.e., the }) of the scope of
- object f:
-
- void someCode()
- {
- File f;
-
- // ... [This code that should execute when f is still open] ...
-
- // <-- We want the side-effect of f's destructor here!
-
- // ... [This code that should execute after f is closed] ...
- }
-
- There is a simple solution to this problem[11.7]. But in the mean time,
- remember: Do not explicitly call the destructor![11.5]
-
- ==============================================================================
-
- [11.7] OK, OK already; I won't explicitly call the destructor of a local; but
- how do I handle the above situation?
-
- [For context, please read the previous FAQ[11.6]].
-
- Simply wrap the extent of the lifetime of the local in an artificial block {
- ... }:
-
- void someCode()
- {
- {
- File f;
- // ... [This code will execute when f is still open] ...
- }
- // ^-- f's destructor will automagically be called here!
-
- // ... [This code will execute after f is closed] ...
- }
-
- ==============================================================================
-
- [11.8] What if I can't wrap the local in an artificial block?
-
- Most of the time, you can limit the lifetime of a local by wrapping the local
- in an artificial block ({ ... })[11.7]. But if for some reason you can't do
- that, add a member function that has a similar effect as the destructor. But
- do not call the destructor itself!
-
- For example, in the case of class File, you might add a close() method.
- Typically the destructor will simply call this close() method. Note that the
- close() method will need to mark the File object so a subsequent call won't
- re-close an already-closed File. E.g., it might set the fileHandle_ data
- member to some nonsensical value such as -1, and it might check at the
- beginning to see if the fileHandle_ is already equal to -1:
-
- class File {
- public:
- void close();
- ~File();
- // ...
- private:
- int fileHandle_; // fileHandle_ >= 0 if/only-if it's open
- };
-
- File::~File()
- {
- close();
- }
-
- void File::close()
- {
- if (fileHandle_ >= 0) {
- // ... [Perform some operating-system call to close the file] ...
- fileHandle_ = -1;
- }
- }
-
- Note that the other File methods may also need to check if the fileHandle_ is
- -1 (i.e., check if the File is closed).
-
- ==============================================================================
-
- [11.9] But can I explicitly call a destructor if I've allocated my object with
- new?
-
- Probably not.
-
- Unless you used placement new[11.10], you should simply delete the object
- rather than explicitly calling the destructor. For example, suppose you
- allocated the object via a typical new expression:
-
- Fred* p = new Fred();
-
- Then the destructor Fred::~Fred() will automagically get called when you delete
- it via:
-
- delete p; // Automagically calls p->~Fred()
-
- You should not explicitly call the destructor, since doing so won't release the
- memory that was allocated for the Fred object itself. Remember: delete p does
- two things[16.8]: it calls the destructor and it deallocates the memory.
-
- ==============================================================================
-
- [11.10] What is "placement new" and why would I use it?
-
- There are many uses of placement new. The simplest use is to place an object
- at a particular location in memory. This is done by supplying the place as a
- pointer parameter to the new part of a new expression:
-
- #include <new.h> // Must #include this to use "placement new"
- #include "Fred.h" // Declaration of class Fred
-
- void someCode()
- {
- char memory[sizeof(Fred)]; // Line #1
- void* place = memory; // Line #2
-
- Fred* f = new(place) Fred(); // Line #3 (see "DANGER" below)
- // The pointers f and place will be equal
-
- // ...
- }
-
- Line #1 creates an array of sizeof(Fred) bytes of memory, which is big enough
- to hold a Fred object. Line #2 creates a pointer place that points to the
- first byte of this memory (experienced C programmers will note that this step
- was unnecessary; it's there only to make the code more obvious). Line #3
- essentially just calls the constructor Fred::Fred(). The this pointer in the
- Fred constructor will be equal to place. The returned pointer f will therefore
- be equal to place.
-
- ADVICE: Don't use this "placement new" syntax unless you have to. Use it only
- when you really care that an object is placed at a particular location in
- memory. For example, when your hardware has a memory-mapped I/O timer device,
- and you want to place a Clock object at that memory location.
-
- DANGER: You are taking sole responsibility that the pointer you pass to the
- "placement new" operator points to a region of memory that is big enough and is
- properly aligned for the object type that you're creating. Neither the
- compiler nor the run-time system make any attempt to check whether you did this
- right. If your Fred class needs to be aligned on a 4 byte boundary but you
- supplied a location that isn't properly aligned, you can have a serious
- disaster on your hands (if you don't know what "alignment" means, please don't
- use the placement new syntax). You have been warned.
-
- You are also solely responsible for destructing the placed object. This is
- done by explicitly calling the destructor:
-
- void someCode()
- {
- char memory[sizeof(Fred)];
- void* p = memory;
- Fred* f = new(p) Fred();
- // ...
- f->~Fred(); // Explicitly call the destructor for the placed object
- }
-
- This is about the only time you ever explicitly call a destructor.
-
- ==============================================================================
-
- [11.11] When I write a destructor, do I need to explicitly call the destructors
- for my member objects?
-
- No. You never need to explicitly call a destructor (except with placement
- new[11.10]).
-
- A class's destructor (whether or not you explicitly define one) automagically
- invokes the destructors for member objects. They are destroyed in the reverse
- order they appear within the declaration for the class.
-
- class Member {
- public:
- ~Member();
- // ...
- };
-
- class Fred {
- public:
- ~Fred();
- // ...
- private:
- Member x_;
- Member y_;
- Member z_;
- };
-
- Fred::~Fred()
- {
- // Compiler automagically calls z_.~Member()
- // Compiler automagically calls y_.~Member()
- // Compiler automagically calls x_.~Member()
- }
-
- ==============================================================================
-
- [11.12] When I write a derived class's destructor, do I need to explicitly call
- the destructor for my base class?
-
- No. You never need to explicitly call a destructor (except with placement
- new[11.10]).
-
- A derived class's destructor (whether or not you explicitly define one)
- automagically invokes the destructors for base class subobjects. Base classes
- are destructed after member objects. In the event of multiple inheritance,
- direct base classes are destructed in the reverse order of their appearance in
- the inheritance list.
-
- class Member {
- public:
- ~Member();
- // ...
- };
-
- class Base {
- public:
- virtual ~Base(); // A virtual destructor[20.4]
- // ...
- };
-
- class Derived : public Base {
- public:
- ~Derived();
- // ...
- private:
- Member x_;
- };
-
- Derived::~Derived()
- {
- // Compiler automagically calls x_.~Member()
- // Compiler automagically calls Base::~Base()
- }
-
- Note: Order dependencies with virtual inheritance are trickier. If you are
- relying on order dependencies in a virtual inheritance hierarchy, you'll need a
- lot more information than is in this FAQ.
-
- ==============================================================================
-
- SECTION [12]: Assignment operators
-
-
- [12.1] What is "self assignment"?
-
- Self assignment is when someone assigns an object with itself. For example,
-
- #include "Fred.hpp" // Declares class Fred
-
- void userCode(Fred& x)
- {
- x = x; // Self-assignment
- }
-
- Obviously no one ever explicitly does a self assignment like the above, but
- since more than one pointer or reference can point to the same object
- (aliasing), it is possible to have self assignment without knowning it:
-
- #include "Fred.hpp" // Declares class Fred
-
- void userCode(Fred& x, Fred& y)
- {
- x = y; // Could be self-assignment if &x == &y
- }
-
- main()
- {
- Fred z;
- userCode(z, z);
- }
-
- ==============================================================================
-
- [12.2] Why should I worry about "self assignment"?
-
- If you don't worry about self assignment[12.1], you'll expose your users to
- some very subtle bugs that have very subtle and often disastrous symptoms. For
- example, the following class will cause a complete disaster in the case of
- self-assignment:
-
- class Wilma { };
-
- class Fred {
- public:
- Fred() : p_(new Wilma()) { }
- Fred(const Fred& f) : p_(new Wilma(*f.p_)) { }
- ~Fred() { delete p_; }
- Fred& operator= (const Fred& f)
- {
- // Bad code: Doesn't handle self-assignment!
- delete p_; // Line #1
- p_ = new Wilma(*f.p_); // Line #2
- return *this;
- }
- private:
- Wilma* p_;
- };
-
- If someone assigns a Fred object with itself, line #1 deletes both this->p_ and
- f.p_ since *this and f are the same object. But line #2 uses *f.p_, which is
- no longer a valid object. This will likely cause a major disaster.
-
- The bottom line is that you the author of class Fred are responsible to make
- sure self-assignment on a Fred object is inocuous[12.3]. Do not assume that
- users won't ever do that to your objects. It is your fault if your object
- crashes when it gets a self-assignment.
-
- Aside: the above Fred::operator= (const Fred&) has a second problem: If an
- exception is thrown[17] while evaluating new Wilma(*f.p_) (e.g., an
- out-of-memory exception[16.5] or an exception in Wilma's copy
- constructor[17.1]), this->p_ will be a dangling pointer -- it will
- point to memory that is no longer valid. This can be solved by allocating
- the new objects before deleting the old objects.
-
- ==============================================================================
-
- [12.3] OK, OK, already; I'll handle self-assignment. How do I do it?
-
- You should worry about self assignment every time you create a class[12.2].
- This does not mean that you need to add extra code to all your classes: as long
- as your objects gracefully handle self assignment, it doesn't matter whether
- you had to add extra code or not.
-
- If you do need to add extra code to your assignment operator, here's a simple
- and effective technique:
-
- Fred& Fred::operator= (const Fred& f)
- {
- if (this == &f) return *this; // Gracefully handle self assignment[12.1]
-
- // Put the normal assignment duties here...
-
- return *this;
- }
-
- This explicit test isn't always necessary. For example, if you were to fix the
- assignment operator in the previous FAQ[12.2] to handle exceptions thrown by
- new[16.5] and/or exceptions thrown by the copy constructor[17.1] of class
- Wilma, you might produce the following code. Note that this code has the
- (pleasant) side effect of automatically handling self assignment as well:
-
- Fred& operator= (const Fred& f)
- {
- // This code gracefully (albeit implicitly) handles self assignment[12.1]
- Wilma* tmp = new Wilma(*f.p_); // It would be OK if an exception[17] got thrown here
- delete p_;
- p_ = tmp;
- return *this;
- }
-
- Some programmers want to add "if (this == &f) return *this;" to make self
- assignment more efficient. This is generally the wrong tradeoff. If self
- assignment only occurs once in a thousand times, the if would waste cycles in
- 99.9% of the time (a test-and-branch can put a bubble in the pipeline of many
- superscalar processors).
-
- ==============================================================================
-
- SECTION [13]: Operator overloading
-
-
- [13.1] What's the deal with operator overloading?
-
- It allows you to provide an intuitive interface to users of your class.
-
- Operator overloading allows C/C++ operators to have user-defined meanings on
- user-defined types (classes). Overloaded operators are syntactic sugar for
- function calls:
-
- class Fred {
- public:
- // ...
- };
-
- #if 0
-
- // Without operator overloading:
- Fred add(Fred, Fred);
- Fred mul(Fred, Fred);
-
- Fred f(Fred a, Fred b, Fred c)
- {
- return add(add(mul(a,b), mul(b,c)), mul(c,a)); // Yuk...
- }
-
- #else
-
- // With operator overloading:
- Fred operator+ (Fred, Fred);
- Fred operator* (Fred, Fred);
-
- Fred f(Fred a, Fred b, Fred c)
- {
- return a*b + b*c + c*a;
- }
-
- #endif
-
- ==============================================================================
-
- [13.2] What are the benefits of operator overloading?
-
- By overloading standard operators on a class, you can exploit the intuition of
- the users of that class. This lets users program in the language of the
- problem domain rather than in the language of the machine.
-
- The ultimate goal is to reduce both the learning curve and the defect rate.
-
- ==============================================================================
-
- [13.3] What are some examples of operator overloading?
-
- Here are a few of the many examples of operator overloading:
- * myString + yourString might concatenate two string objects
- * myDate++ might increment a Date object
- * a * b might multiply two Number objects
- * a[i] might access an element of an Array object
- * x = *p might dereference a "smart pointer" that actually "points" to a disk
- record -- it could actually seek to the location on disk where p "points"
- and return the appropriate record into x
-
- ==============================================================================
-
- [13.4] But operator overloading makes my class look ugly; isn't it supposed to
- make my code clearer?
-
- Operator overloading makes life easier for the users of a class[13.2], not for
- the developer of the class!
-
- Consider the following example.
-
- class Array {
- public:
- int& operator[] (unsigned i); // Some people don't like this syntax
- // ...
- };
-
- inline
- int& Array::operator[] (unsigned i) // Some people don't like this syntax
- {
- // ...
- }
-
- Some people don't like the keyword operator or the somewhat funny syntax that
- goes with it in the body of the class itself. But the operator overloading
- syntax isn't supposed to make life easier for the developer of a class. It's
- supposed to make life easier for the users of the class:
-
- main()
- {
- Array a;
- a[3] = 4; // User code should be obvious and easy to understand...
- }
-
- Remember: in a reuse-oriented world, there will usually be many people who use
- your class, but there is only one person who builds it (yourself); therefore
- you should do things that favor the many rather than the few.
-
- ==============================================================================
-
- [13.5] What operators can/cannot be overloaded?
-
- Most can be overloaded. The only C operators that can't be are . and ?: (and
- sizeof, which is technically an operator). C++ adds a few of its own
- operators, most of which can be overloaded except :: and .*.
-
- Here's an example of the subscript operator (it returns a reference). First
- without operator overloading:
-
- class Array {
- public:
- #if 0
- int& elem(unsigned i) { if (i > 99) error(); return data[i]; }
- #else
- int& operator[] (unsigned i) { if (i > 99) error(); return data[i]; }
- #endif
- private:
- int data[100];
- };
-
- main()
- {
- Array a;
-
- #if 0
- a.elem(10) = 42;
- a.elem(12) += a.elem(13);
- #else
- a[10] = 42;
- a[12] += a[13];
- #endif
- }
-
- ==============================================================================
-
- [13.6] Can I overload operator== so it lets me compare two char[] using a
- string comparison?
-
- No: at least one operand of any overloaded operator must be of some class type.
-
- But even if C++ allowed you to do this, which it doesn't, you wouldn't want to
- do it anyway since you really should be using a string-like class rather than
- an array of char in the first place[17.3] since arrays are evil[21.5].
-
- ==============================================================================
-
- [13.7] Can I create a operator** for "to-the-power-of" operations?
-
- Nope.
-
- The names of, precedence of, associativity of, and arity of operators is fixed
- by the language. There is no operator** in C++, so you cannot create one for a
- class type.
-
- If you're in doubt, consider that x ** y is the same as x * (*y) (in other
- words, the compiler assumes y is a pointer). Besides, operator overloading is
- just syntactic sugar for function calls. Although this particular syntactic
- sugar can be very sweet, it doesn't add anything fundamental. I suggest you
- overload pow(base,exponent) (a double precision version is in <math.h>).
-
- By the way, operator^ can work for to-the-power-of, except it has the wrong
- precedence and associativity.
-
- ==============================================================================
-
- [13.8] How do I create a subscript operator for a Matrix class?
-
- Use operator() rather than operator[].
-
- When you have multiple subscripts, the cleanest way to do it is with operator()
- rather than with operator[]. The reason is that operator[] always takes
- exactly one parameter, but operator() can take any number of parameters (in the
- case of a rectangular matrix, two paramters are needed).
-
- For example:
-
- class Matrix {
- public:
- Matrix(unsigned rows, unsigned cols);
- double& operator() (unsigned row, unsigned col);
- double operator() (unsigned row, unsigned col) const;
- // ...
- ~Matrix(); // Destructor
- Matrix(const Matrix& m); // Copy constructor
- Matrix& operator= (const Matrix& m); // Assignment operator
- // ...
- private:
- unsigned rows_, cols_;
- double* data_;
- };
-
- inline
- Matrix::Matrix(unsigned rows, unsigned cols)
- : rows_ (rows),
- cols_ (cols),
- data_ (new double[rows * cols])
- {
- if (rows == 0 || cols == 0)
- throw BadIndex("Matrix constructor has 0 size");
- }
-
- inline
- Matrix::~Matrix()
- {
- delete[] data_;
- }
-
- inline
- double& Matrix::operator() (unsigned row, unsigned col)
- {
- if (row >= rows_ || col >= cols_)
- throw BadIndex("Matrix subscript out of bounds");
- return data_[cols_*row + col];
- }
-
- inline
- double Matrix::operator() (unsigned row, unsigned col) const
- {
- if (row >= rows_ || col >= cols_)
- throw BadIndex("const Matrix subscript out of bounds");
- return data_[cols_*row + col];
- }
-
- Then you can access an element of Matrix m using m(i,j) rather than m[i][j]:
-
- main()
- {
- Matrix m;
- m(5,8) = 106.15;
- cout << m(5,8);
- // ...
- }
-
- ==============================================================================
-
- [13.9] Should I design my classes from the outside (interfaces first) or from
- the inside (data first)?
-
- From the outside!
-
- A good interface provides a simplified view that is expressed in the vocabulary
- of a user[7.3]. In the case of OO software, the interface is normally to a
- class or a tight group of classes[14.2].
-
- First think about what the object logically represents, not how you intend to
- physically build it. For example, suppose you have a Stack class that will be
- built by containing a LinkedList:
-
- class Stack {
- public:
- // ...
- private:
- LinkedList list_;
- };
-
- Should the Stack have a get() method that returns the LinkedList? Or a set()
- method that takes a LinkedList? Or a constructor that takes a LinkedList?
- Obviously the answer is No, since you should design your interfaces from the
- outside-in. I.e., users of Stack objects don't care about LinkedLists; they
- care about pushing and popping.
-
- Now for another example that is a bit more subtle. Suppose class LinkedList is
- built using a linked list of Node objects, where each Node object has a pointer
- to the next Node:
-
- class Node { /*...*/ };
-
- class LinkedList {
- public:
- // ...
- private:
- Node* first_;
- };
-
- Should the LinkedList class have a get() method that will let users access the
- first Node? Should the Node object have a get() method that will let users
- follow that Node to the next Node in the chain? In other words, what should a
- LinkedList look like from the outside? Is a LinkedList really a chain of Node
- objects? Or is that just an implementation detail? And if it is just an
- implementation detail, how will the LinkedList let users access each of the
- elements in the LinkedList one at a time?
-
- One man's answer: A LinkedList is not a chain of Nodes. That may be how it is
- built, but that is not what it is. What it is is a sequence of elements.
- Therefore the LinkedList abstraction should provide a "LinkedListIterator"
- class as well, and that "LinkedListIterator" might have an operator++ to go to
- the next element, and it might have a get()/set() pair to access its value
- stored in the Node (the value in the Node element is solely the responsibility
- of the LinkedList user, which is why there is a get()/set() pair that allows
- the user to freely manipulate that value).
-
- Starting from the user's perspective, we might want our LinkedList class to
- support operations that look similar to accessing an array using pointer
- arithmetic:
-
- void userCode(LinkedList& a)
- {
- for (LinkedListIterator p = a.begin(); p != a.end(); ++p)
- cout << *p << '\n';
- }
-
- To implement this interface, LinkedList will need a begin() method and an end()
- method. These return a "LinkedListIterator" object. The "LinkedListIterator"
- will need a method to go forward, ++p; a method to access the current element,
- *p; and a comparison operator, p != a.end().
-
- The code follows. The key insight is that the LinkedList class does not have
- any methods that lets users access the Nodes. Nodes are an implementation
- technique that is completely buried. The LinkedList class could have its
- internals replaced with a doubly linked list, or even an array, and the only
- difference would be some performance differences with the prepend(elem) and
- append(elem) methods.
-
- #include <assert.h> // Poor man's exception handling
-
- typedef int bool; // Someday we won't have to do this
-
- class LinkedListIterator;
- class LinkedList;
-
- class Node {
- // No public members; this is a "private class"
- friend LinkedListIterator; // A friend class[14]
- friend LinkedList;
- Node* next_;
- int elem_;
- };
-
- class LinkedListIterator {
- public:
- bool operator== (LinkedListIterator i) const;
- bool operator!= (LinkedListIterator i) const;
- void operator++ (); // Go to the next element
- int& operator* (); // Access the current element
- private:
- LinkedListIterator(Node* p);
- Node* p_;
- };
-
- class LinkedList {
- public:
- void append(int elem); // Adds elem after the end
- void prepend(int elem); // Adds elem before the beginning
- // ...
- LinkedListIterator begin();
- LinkedListIterator end();
- // ...
- private:
- Node* first_;
- };
-
- Here are the methods that are obviously inlinable (probably in the same header
- file):
-
- inline bool LinkedListIterator::operator== (LinkedListIterator i) const
- {
- return p_ == i.p_;
- }
-
- inline bool LinkedListIterator::operator!= (LinkedListIterator i) const
- {
- return p_ != i.p_;
- }
-
- inline void LinkedListIterator::operator++()
- {
- assert(p_ != NULL); // or if (p_==NULL) throw ...
- p_ = p_->next_;
- }
-
- inline int& LinkedListIterator::operator*()
- {
- assert(p_ != NULL); // or if (p_==NULL) throw ...
- return p_->elem_;
- }
-
- inline LinkedListIterator::LinkedListIterator(Node* p)
- : p_(p)
- { }
-
- inline LinkedListIterator LinkedList::begin()
- {
- return first_;
- }
-
- inline LinkedListIterator LinkedList::end()
- {
- return NULL;
- }
-
- Conclusion: The linked list had two different kinds of data. The values of the
- elements stored in the linked list are the responsibility of the user of the
- linked list (and only the user; the linked list itself makes no attempt to
- prohibit users from changing the third element to 5), and the linked list's
- infrastructure data (next pointers, etc.), whose values are the responsibility
- of the linked list (and only the linked list; e.g., the linked list does not
- let users change (or even look at!) the various next pointers).
-
- Thus the only get()/set() methods were to get and set the elements of the
- linked list, but not the infrastructure of the linked list. Since the linked
- list hides the infrastructure pointers/etc., it is able to make very strong
- promises regarding that infrastructure (e.g., if it was a doubly linked list,
- it might guarantee that every forward pointer was matched by a backwards
- pointer from the next Node).
-
- So, we see here an example of where the values of some of a class's data is the
- responsibility of users (in which case the class needs to have get()/set()
- methods for that data) but the data that the class wants to control does not
- necessarily have get()/set() methods.
-
- ==============================================================================
-
-